/**
 *
 */
package javax.jmdns.impl;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import javax.jmdns.JmmDNS;
import javax.jmdns.impl.tasks.RecordReaper;
import javax.jmdns.impl.tasks.Responder;
import javax.jmdns.impl.tasks.resolver.ServiceInfoResolver;
import javax.jmdns.impl.tasks.resolver.ServiceResolver;
import javax.jmdns.impl.tasks.resolver.TypeResolver;
import javax.jmdns.impl.tasks.state.Announcer;
import javax.jmdns.impl.tasks.state.Canceler;
import javax.jmdns.impl.tasks.state.Prober;
import javax.jmdns.impl.tasks.state.Renewer;

/**
 * This class is used by JmDNS to start the various task required to run the DNS discovery. This interface is only there
 * in order to support MANET modifications.
 * <p>
 * <b>Note: </b> This is not considered as part of the general public API of JmDNS.
 * </p>
 * @author Pierre Frisch
 */
public interface DNSTaskStarter {

   /**
    * DNSTaskStarter.Factory enable the creation of new instance of DNSTaskStarter.
    */
   public static final class Factory {

      private static volatile Factory _instance;
      private final ConcurrentMap<JmDNSImpl, DNSTaskStarter> _instances;

      /**
       * This interface defines a delegate to the DNSTaskStarter class to enable subclassing.
       */
      public static interface ClassDelegate {

         /**
          * Allows the delegate the opportunity to construct and return a different DNSTaskStarter.
          * @param jmDNSImpl jmDNS instance
          * @return Should return a new DNSTaskStarter Object.
          * @see #classDelegate()
          * @see #setClassDelegate(ClassDelegate anObject)
          */
         public DNSTaskStarter newDNSTaskStarter(JmDNSImpl jmDNSImpl);
      }

      private static final AtomicReference<Factory.ClassDelegate> _databaseClassDelegate = new AtomicReference<Factory.ClassDelegate>();

      private Factory() {
         super();
         _instances = new ConcurrentHashMap<JmDNSImpl, DNSTaskStarter>(20);
      }

      /**
       * Assigns <code>delegate</code> as DNSTaskStarter's class delegate. The class delegate is optional.
       * @param delegate The object to set as DNSTaskStarter's class delegate.
       * @see #classDelegate()
       * @see JmmDNS.Factory.ClassDelegate
       */
      public static void setClassDelegate(Factory.ClassDelegate delegate) {
         _databaseClassDelegate.set(delegate);
      }

      /**
       * Returns DNSTaskStarter's class delegate.
       * @return DNSTaskStarter's class delegate.
       * @see #setClassDelegate(ClassDelegate anObject)
       * @see JmmDNS.Factory.ClassDelegate
       */
      public static Factory.ClassDelegate classDelegate() {
         return _databaseClassDelegate.get();
      }

      /**
       * Returns a new instance of DNSTaskStarter using the class delegate if it exists.
       * @param jmDNSImpl jmDNS instance
       * @return new instance of DNSTaskStarter
       */
      protected static DNSTaskStarter newDNSTaskStarter(JmDNSImpl jmDNSImpl) {
         DNSTaskStarter instance = null;
         Factory.ClassDelegate delegate = _databaseClassDelegate.get();
         if (delegate != null) {
            instance = delegate.newDNSTaskStarter(jmDNSImpl);
         }
         return (instance != null ? instance : new DNSTaskStarterImpl(jmDNSImpl));
      }

      /**
       * Return the instance of the DNSTaskStarter Factory.
       * @return DNSTaskStarter Factory
       */
      public static Factory getInstance() {
         if (_instance == null) {
            synchronized (DNSTaskStarter.Factory.class) {
               if (_instance == null) {
                  _instance = new Factory();
               }
            }
         }
         return _instance;
      }

      /**
       * Return the instance of the DNSTaskStarter for the JmDNS.
       * @param jmDNSImpl jmDNS instance
       * @return the DNSTaskStarter
       */
      public DNSTaskStarter getStarter(JmDNSImpl jmDNSImpl) {
         DNSTaskStarter starter = _instances.get(jmDNSImpl);
         if (starter == null) {
            _instances.putIfAbsent(jmDNSImpl, newDNSTaskStarter(jmDNSImpl));
            starter = _instances.get(jmDNSImpl);
         }
         return starter;
      }

   }

   public static final class DNSTaskStarterImpl implements DNSTaskStarter {

      private final JmDNSImpl _jmDNSImpl;

      /**
       * The timer is used to dispatch all outgoing messages of JmDNS. It is also used to dispatch maintenance tasks for
       * the DNS cache.
       */
      private final Timer _timer;

      /**
       * The timer is used to dispatch maintenance tasks for the DNS cache.
       */
      private final Timer _stateTimer;

      public static class StarterTimer extends Timer {

         // This is needed because in some case we cancel the timers before all the task have finished running and in
         // some case they will try to reschedule
         private volatile boolean _cancelled;

         /**
             *
             */
         public StarterTimer() {
            super();
            _cancelled = false;
         }

         /**
          * @param isDaemon
          */
         public StarterTimer(boolean isDaemon) {
            super(isDaemon);
            _cancelled = false;
         }

         /**
          * @param name
          * @param isDaemon
          */
         public StarterTimer(String name, boolean isDaemon) {
            super(name, isDaemon);
            _cancelled = false;
         }

         /**
          * @param name
          */
         public StarterTimer(String name) {
            super(name);
            _cancelled = false;
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#cancel()
          */

         @Override
         public void cancel() {
            _cancelled = true;
            super.cancel();
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#schedule(java.util.TimerTask, long)
          */

         @Override
         public synchronized void schedule(TimerTask task, long delay) {
            if (_cancelled)
               return;
            super.schedule(task, delay);
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#schedule(java.util.TimerTask, java.util.Date)
          */

         @Override
         public synchronized void schedule(TimerTask task, Date time) {
            if (_cancelled)
               return;
            super.schedule(task, time);
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#schedule(java.util.TimerTask, long, long)
          */

         @Override
         public synchronized void schedule(TimerTask task, long delay, long period) {
            if (_cancelled)
               return;
            super.schedule(task, delay, period);
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#schedule(java.util.TimerTask, java.util.Date, long)
          */

         @Override
         public synchronized void schedule(TimerTask task, Date firstTime, long period) {
            if (_cancelled)
               return;
            super.schedule(task, firstTime, period);
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#scheduleAtFixedRate(java.util.TimerTask, long, long)
          */

         @Override
         public synchronized void scheduleAtFixedRate(TimerTask task, long delay, long period) {
            if (_cancelled)
               return;
            super.scheduleAtFixedRate(task, delay, period);
         }

         /*
          * (non-Javadoc)
          * @see java.util.Timer#scheduleAtFixedRate(java.util.TimerTask, java.util.Date, long)
          */

         @Override
         public synchronized void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
            if (_cancelled)
               return;
            super.scheduleAtFixedRate(task, firstTime, period);
         }

      }

      public DNSTaskStarterImpl(JmDNSImpl jmDNSImpl) {
         super();
         _jmDNSImpl = jmDNSImpl;
         _timer = new StarterTimer("JmDNS(" + _jmDNSImpl.getName() + ").Timer", true);
         _stateTimer = new StarterTimer("JmDNS(" + _jmDNSImpl.getName() + ").State.Timer", false);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#purgeTimer()
       */

      public void purgeTimer() {
         _timer.purge();
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#purgeStateTimer()
       */

      public void purgeStateTimer() {
         _stateTimer.purge();
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#cancelTimer()
       */

      public void cancelTimer() {
         _timer.cancel();
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#cancelStateTimer()
       */

      public void cancelStateTimer() {
         _stateTimer.cancel();
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startProber()
       */

      public void startProber() {
         new Prober(_jmDNSImpl).start(_stateTimer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startAnnouncer()
       */

      public void startAnnouncer() {
         new Announcer(_jmDNSImpl).start(_stateTimer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startRenewer()
       */

      public void startRenewer() {
         new Renewer(_jmDNSImpl).start(_stateTimer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startCanceler()
       */

      public void startCanceler() {
         new Canceler(_jmDNSImpl).start(_stateTimer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startReaper()
       */

      public void startReaper() {
         new RecordReaper(_jmDNSImpl).start(_timer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startServiceInfoResolver(javax.jmdns.impl.ServiceInfoImpl)
       */

      public void startServiceInfoResolver(ServiceInfoImpl info) {
         new ServiceInfoResolver(_jmDNSImpl, info).start(_timer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startTypeResolver()
       */

      public void startTypeResolver() {
         new TypeResolver(_jmDNSImpl).start(_timer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startServiceResolver(java.lang.String)
       */

      public void startServiceResolver(String type) {
         new ServiceResolver(_jmDNSImpl, type).start(_timer);
      }

      /*
       * (non-Javadoc)
       * @see javax.jmdns.impl.DNSTaskStarter#startResponder(javax.jmdns.impl.DNSIncoming, int)
       */

      public void startResponder(DNSIncoming in, int port) {
         new Responder(_jmDNSImpl, in, port).start(_timer);
      }
   }

   /**
    * Purge the general task timer
    */
   public void purgeTimer();

   /**
    * Purge the state task timer
    */
   public void purgeStateTimer();

   /**
    * Cancel the generals task timer
    */
   public void cancelTimer();

   /**
    * Cancel the state task timer
    */
   public void cancelStateTimer();

   /**
    * Start a new prober task
    */
   public void startProber();

   /**
    * Start a new announcer task
    */
   public void startAnnouncer();

   /**
    * Start a new renewer task
    */
   public void startRenewer();

   /**
    * Start a new canceler task
    */
   public void startCanceler();

   /**
    * Start a new reaper task. There is only supposed to be one reaper running at a time.
    */
   public void startReaper();

   /**
    * Start a new service info resolver task
    * @param info service info to resolve
    */
   public void startServiceInfoResolver(ServiceInfoImpl info);

   /**
    * Start a new service type resolver task
    */
   public void startTypeResolver();

   /**
    * Start a new service resolver task
    * @param type service type to resolve
    */
   public void startServiceResolver(String type);

   /**
    * Start a new responder task
    * @param in incoming message
    * @param port incoming port
    */
   public void startResponder(DNSIncoming in, int port);

}
